﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

using System.Diagnostics;
using MessageCallAspect.Aspect.Cache;
using MessageCallAspect.Inspector;
using System.Security.Permissions;
using Syk.Library.Extensions;
using Syk.Library.Common;
using Syk.Library.Cache;

namespace MessageCallAspect.Aspect.Cache
{
    [DebuggerStepThrough]
    public class CacheMessageSink : IMessageSink
    {
        #region fields

        private readonly IMessageSink m_NextSink;
        private readonly ICacheProvider m_CacheProvider;
        public TimeSpan Timeout { get; set; }
        private readonly object m_Target;
        private readonly ICachePolicy m_CachePolicy;
        private readonly ITargetInspectorPolicy m_TargetInspectorPolicy;
        #endregion

        #region ctor

        public CacheMessageSink(object target, IMessageSink nextSink, IServiceProvider context, ICachePolicy cachePolicy)
        {
            m_CacheProvider = context.GetService<ICacheProvider>();
            VallidationHelper.ArgumentNotNull(() => m_CacheProvider);
            m_Target = target;
            m_NextSink = nextSink;
            m_CachePolicy = cachePolicy;
            Timeout = TimeSpan.MaxValue;

            m_TargetInspectorPolicy = context.GetService<ITargetInspectorPolicy>();
        }

        #endregion

        #region IMessageSink Members

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            throw new NotImplementedException();
        }

        public virtual IMessageSink NextSink
        {
            get
            {
                return m_NextSink;
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
        public IMessage SyncProcessMessage(IMessage msg)
        {
            Debug.WriteLine("Step into: " + this.GetType().Name);
            IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)msg);
            object uid = CreateMessageKey(m_Target.GetType(), (MethodInfo)methodMessage.MethodBase, methodMessage.Args);
            object value = m_CacheProvider.Get(uid, Timeout);
            IMethodReturnMessage returnMessage = null;
            if (value != null)
            {
                Debug.WriteLine(" [found in cache]");
                returnMessage = new ReturnMessage(value, methodMessage.Args, methodMessage.ArgCount, methodMessage.LogicalCallContext, methodMessage);
            }
            else
            {
                IMessage msgReturn = NextSink.SyncProcessMessage(msg);
                Debug.WriteLine("Step into: " + this.GetType().Name);
                returnMessage = (IMethodReturnMessage)msgReturn;
                if (returnMessage.Exception == null)
                {
                    if ((m_CachePolicy == null
                        || m_CachePolicy.AllowCaching(returnMessage.ReturnValue))
                        && (m_TargetInspectorPolicy == null || m_TargetInspectorPolicy.CachingPredicate(this)))
                    {
                        m_CacheProvider.Set(uid, returnMessage.ReturnValue, Timeout);
                    }
                }
            }
            return returnMessage;
        }

        #endregion

        protected virtual object CreateMessageKey(Type target, MethodInfo methodInfo, object[] args)
        {

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("{0}[{1}][{2}]:", target.FullName, methodInfo.MethodHandle.Value, methodInfo.Name);
            args = args.Where(arg => arg != null).ToArray();
            foreach (object arg in args)
            {
                sb.Append(arg.GetHashCode());
                sb.Append(",");
            }
            sb.Remove(sb.Length - 1, 1);
            return sb.ToString();
        }
    }
}
